package com.omni.monitor.plugin.trace_monitor;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.omni.monitor.model.trace_monitor.TraceNode;
import com.omni.monitor.plugin.utils.MonitorConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJAroundAdvice;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.aspectj.SingletonAspectInstanceFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;

import javax.annotation.PostConstruct;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Aspect
@Component
@Slf4j
public class ServiceTraceAspectV2 {
    private final TraceContext traceContext;
    private final MonitorConfig monitorConfig;
    private final Set<String> excludeMethodSet = new HashSet<>();
    private final Set<String> excludeServiceSet = new HashSet<>();
    private final ConcurrentHashMap<Class<?>, String> proxyServiceNameCache = new ConcurrentHashMap<>();

    public ServiceTraceAspectV2(TraceContext traceContext, MonitorConfig monitorConfig) {
        this.traceContext = traceContext;
        this.monitorConfig = monitorConfig;
    }

    @PostConstruct
    public void initExcludeRules() {
        if (StringUtils.isBlank(monitorConfig.getExclude_service_method())) {
            return;
        }
        String[] rules = monitorConfig.getExclude_service_method().split(",");
        for (String rule : rules) {
            String trimRule = rule.trim();
            if (trimRule.endsWith(".*")) {
                excludeServiceSet.add(trimRule.substring(0, trimRule.indexOf(".*")));
            } else {
                excludeMethodSet.add(trimRule);
            }
        }
    }

    @Bean
    @ConditionalOnProperty(name = "trace-monitor.enabled", havingValue = "aaaa", matchIfMissing = false)
    public Advisor dynamicPointcutAdvisor() throws NoSuchMethodException {
        String expression = monitorConfig.pointcutExpression();
        if (StringUtils.isBlank(expression)) {
            throw new IllegalArgumentException("切入点表达式不能为空，请检查配置");
        }
        // 1. 获取配置的切入点表达式
        String optimizedExpression = expression + " && !within($Proxy*) && !within(org.springframework..*)";

        if (expression.trim().isEmpty()) {
            throw new IllegalArgumentException("切入点表达式不能为空，请检查配置");
        }

        // 2. 创建切入点并设置表达式
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(optimizedExpression); // 从配置读取表达式

        // 3. 获取环绕通知方法（traceServiceCall）
        Method adviceMethod = ServiceTraceAspect.class.getMethod("traceServiceCall", ProceedingJoinPoint.class);

        // 4. 关键修复：用SingletonAspectInstanceFactory包装切面实例
        // 解决 "ServiceTraceAspect cannot be cast to AspectInstanceFactory" 错误
        SingletonAspectInstanceFactory aspectInstanceFactory = new SingletonAspectInstanceFactory(this);

        // 5. 创建环绕通知（使用正确的工厂类）
        AspectJAroundAdvice aroundAdvice = new AspectJAroundAdvice(adviceMethod, pointcut, aspectInstanceFactory // 传入工厂类而非直接传入this
        );

        // 6. 创建Advisor并关联切入点和通知
        return new DefaultPointcutAdvisor(pointcut, aroundAdvice);
    }

    //@Around("execution(* *(..)) && @within(org.springframework.stereotype.Service)")
    public Object traceServiceCall(ProceedingJoinPoint joinPoint) throws Throwable {
        // 1. 获取真实目标类（排除CGLIB/JDK代理）
        Class<?> targetClass = ClassUtils.getUserClass(joinPoint.getTarget());
        String serviceName = targetClass.getSimpleName();

        // 2. 获取方法名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String methodName = signature.getName();
        TraceNode currentNode = enterService(joinPoint, serviceName, targetClass, methodName, signature);
        Object result = null;
        boolean success = true;
        try {
            // 5. 执行原方法
            result = joinPoint.proceed();
            return result;
        } catch (Throwable e) {
            success = false;
            // 6. 捕获异常：记录错误日志（含调用链三要素）
            log.error("Service调用失败：{}.{}，错误信息：{}", serviceName, methodName, e.getMessage(), e); // 打印堆栈，方便排查
            throw e;
        } finally {

            exitService(signature, result, currentNode, success);
        }
    }

    private TraceNode enterService(ProceedingJoinPoint joinPoint, String serviceName, Class<?> targetClass, String methodName, MethodSignature signature) {
        if (!"on".equals(monitorConfig.getEnabled())) {
            return null;
        }
        try {
            if (serviceName.startsWith("$Proxy")) {
                serviceName = proxyServiceNameCache.computeIfAbsent(targetClass, this::resolveProxyServiceName);
            }
            if (!monitor(serviceName, methodName)) {
                return null;
            }
            Map<String, Object> params = "on".equals(monitorConfig.getParameterSwitch())
                    ? buildParams(joinPoint, signature)
                    : null;

            return traceContext.enterService(serviceName, methodName, params);
        } catch (Exception e) {
            log.error("enterService失败：{}.{}", serviceName, methodName, e);
            return null;
        }
    }

    private void exitService(MethodSignature signature, Object result, TraceNode currentNode, boolean success) {
        if (!"on".equals(monitorConfig.getEnabled()) || currentNode == null) {
            return;
        }
        try {
            Map<String, Object> resultMap = "on".equals(monitorConfig.getParameterSwitch())
                    ? Collections.singletonMap(signature.getReturnType().getSimpleName(), result)
                    : null;
            currentNode.setSuccess(success);
            traceContext.exitService(currentNode, resultMap);
        } catch (Exception e) {
            log.error("exitService失败：{}.{}", currentNode.getServiceName(), currentNode.getMethodName(), e);
        }
    }

    private String resolveProxyServiceName(Class<?> targetClass) {
        Class<?>[] interfaces = targetClass.getInterfaces();
        for (Class<?> iface : interfaces) {
            if (iface.getName().endsWith("Mapper") && !iface.equals(BaseMapper.class)) {
                return iface.getSimpleName();
            }
        }
        return targetClass.getSimpleName();
    }

    private Map<String, Object> buildParams(ProceedingJoinPoint joinPoint, MethodSignature signature) {
        Object[] args = joinPoint.getArgs();
        Class<?>[] parameterTypes = signature.getParameterTypes();
        Map<String, Object> params = new HashMap<>(parameterTypes.length);
        for (int i = 0; i < parameterTypes.length; i++) {
            Class<?> paramType = parameterTypes[i];
            if (paramType == null) continue;
            params.put(paramType.getSimpleName(), args != null && i < args.length ? args[i] : "null");
        }
        return params;
    }

    private boolean monitor(String serviceName, String methodName) {
        return !excludeServiceSet.contains(serviceName)
                && !excludeMethodSet.contains(serviceName + "." + methodName);
    }
}